#include <stddef.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#define __USE_GNU
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/select.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <poll.h>

#include "properties.h"
#include "utils.h"

#define PROP_MSG_SETPROP    1
#define PROP_MSG_GETPROP    2
#define PROP_MSG_LISTPROP   3
#define PROP_MSG_SYNCPROP   4
#define PROP_MSG_UNSYNCPROP 5

#define PROPERTY_SOCKET_PATH "/dev/socket/property_service"

typedef struct prop_msg_s {
    unsigned cmd;
    char name[PROP_NAME_MAX];
    char value[PROP_VALUE_MAX];
} prop_msg_t;

static int create_socket() {
    int s = socket(AF_LOCAL, SOCK_STREAM, 0);
    if (s < 0) {
        ERRNO("create socket");
        return -1;
    }

    const char property_service_socket[] = PROPERTY_SOCKET_PATH;
    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(addr));
    strncpy_safe(addr.sun_path, sizeof(addr.sun_path), property_service_socket);
    addr.sun_family = AF_LOCAL;
    socklen_t alen = strlen(property_service_socket) + offsetof(struct sockaddr_un, sun_path) + 1;
    if (TEMP_FAILURE_RETRY(connect(s, (struct sockaddr *)&addr, alen) < 0)) {
        ERRNO("connect to prop server at %s", property_service_socket);
        close(s);
        return -1;
    }
    return s;
}

static int process_no_reply(int s) {
    struct pollfd pollfds[1];
    pollfds[0].fd = s;
    pollfds[0].events = 0;
    const int poll_result = TEMP_FAILURE_RETRY(poll(pollfds, 1, 250 /* ms */));
    int result = -1;
    if (poll_result == 1 && (pollfds[0].revents & POLLHUP) != 0) {
        result = 0;
    } else {
        //Ignore the timeout and treat it like a success anyway.
        //in share mem mode, the value has been set to sharemem,
        //we don't take care about the timeout caused by io busy
        //TODO:fix the system properties design.
        ERROR("set property timeout");
        result = 0;
    }
    return result;
}
static int process_one_reply(int s, prop_msg_t *msg) {
    int size = sizeof(*msg);
    int result = recv(s, msg, size, 0);
    if (result == size) return 0;

    if (result < 0) ERRNO("recv");
    else if (result == 0) ERROR("no reply from server");
    else if (result > 0) ERROR("incompleted msg data, expected %d, actual %d", size, result);
    return -1;
}

typedef void (*prop_callback_t)(const char *key, const char *value, void *cookie);

static int process_multiple_reply(int s, prop_msg_t *msg, prop_callback_t callback, void *cookie) {
    int size = sizeof(*msg);
    int result = -1;
    while (true) {
        result = recv(s, msg, size, 0);
        if (result != size) break;

        if (callback) callback(msg->name, msg->value, cookie);
    }
    if (result == 0) return 0;

    if (result < 0) ERRNO("recv");
    else if (result > 0) ERROR("incomplete msg data, expect %d, actual %d", size, result);
    return -1;
}

static int send_prop_msg(prop_msg_t *msg, prop_callback_t callback, void *cookie) {
    int s = create_socket();
    if (s < 0) return -1;

    int size = sizeof(*msg);
    int r = TEMP_FAILURE_RETRY(send(s, msg, size, 0));

    int result = -1;
    if (r == size) {
        if (msg->cmd == PROP_MSG_SETPROP
            || msg->cmd == PROP_MSG_SYNCPROP
            || msg->cmd == PROP_MSG_UNSYNCPROP) result = process_no_reply(s);
        else if (msg->cmd == PROP_MSG_GETPROP) result = process_one_reply(s, msg);
        else result = process_multiple_reply(s, msg, callback, cookie);
    } else {
        ERRNO("send %d", r);
        result = -1;
    }

    close(s);
    return result;
}

#ifdef PROP_GET_MMAP
#include "properties_mmap.h"
static bool property_get_mmap_initialized = 0;
__attribute__((constructor)) static void property_get_mmap_init() {
    property_get_mmap_initialized = property_mmap_init() == 0;
}
#endif

int property_list(void (*propfn)(const char *key, const char *value, void *cookie), void *cookie)
{
#ifdef PROP_GET_MMAP
    if (property_get_mmap_initialized) {
        property_list_mmap(propfn, cookie);
        return 0;
    }
#endif
    int err;
    prop_msg_t msg;

    memset(&msg, 0, sizeof(msg));
    msg.cmd = PROP_MSG_LISTPROP;

    err = send_prop_msg(&msg, propfn, cookie);
    return 0;
}

static int property_get_socket(const char *key, char *value, const char *default_value)
{
    int err;
    prop_msg_t msg;

    memset(&msg, 0, sizeof(msg));
    msg.cmd = PROP_MSG_GETPROP;

    strncpy_safe(msg.name, sizeof(msg.name), key);
    err = send_prop_msg(&msg, NULL, NULL);
    if (err < 0) {
        ERROR("send_prop_msg error %d", err);
        return err;
    }

    /* In case it's null, just use the default */
    if ((strlen(msg.value) == 0) && (default_value)) {
        DEBUG("the property `%s` get from server is empty", key);
        if (strlen(default_value) >= PROP_VALUE_MAX -1) {
            ERROR("the length of default_value is invalid");
            return -1;
        }
        strcpy(msg.value, default_value);
    }

    strcpy(value, msg.value);

    return 0;
}

int property_get(const char *key, char *value, const char *default_value)
{
    INFO("property_get `%s`, default `%s`", key, default_value);
    char *ret = NULL;

    if (key == NULL) {
        ERROR("key is NULL");
        return -1;
    }
    int keylen = strlen(key);
    if (keylen <= 0 || keylen >= PROP_NAME_MAX) {
        ERROR("invalid key length %d, should 0 < keylen < %d: %s", keylen, PROP_NAME_MAX, key);
        return -1;
    }
    if (value == NULL) {
        ERROR("value is NULL");
        return -1;
    }

#ifdef PROP_GET_MMAP
    if (property_get_mmap_initialized) {
        property_get_mmap(key, value, default_value);
        return strlen(value);
    }
#endif

    if (property_get_socket(key, value, default_value) == 0) {
        DEBUG("property_get_socket `%s` -> `%s`", key, value);
        return strlen(value);
    }

    log_property_failure(key, NULL);
    ERROR("failed to get prop `%s` from server, trying to get from cache", key);

    if (ret) {
        strcpy(value, ret);
        DEBUG("got prop from cache: %s -> %s", key, value);
    } else if (default_value != NULL) {
        strcpy(value, default_value);
    } else {
        *value = '\0';
    }
    return strlen(value);
}

int property_set(const char *key, const char *value)
{
    if (key == NULL) {
        ERROR("key is NULL");
        return -1;
    }
    int keylen = strlen(key);
    if (keylen <= 0 || keylen >= PROP_NAME_MAX) {
        ERROR("invalid key length %d, should 0 < keylen < %d: %s", keylen, PROP_NAME_MAX, key);
        return -1;
    }
    if (value == NULL)  value = "";

    int valuelen = strlen(value);
    if (valuelen >= PROP_VALUE_MAX) {
        ERROR("invalid value length %d, should 0 < length < %d: %s", valuelen, PROP_VALUE_MAX, value);
        return -1;
    }

    LOG_D("property_set `%s`->`%s`", key, value);
    prop_msg_t msg;
    memset(&msg, 0, sizeof(msg));
    msg.cmd = PROP_MSG_SETPROP;
    strncpy_safe(msg.name, sizeof(msg.name), key);
    strncpy_safe(msg.value, sizeof(msg.value), value);

    int err = send_prop_msg(&msg, NULL, NULL);
    if (err < 0) {
        log_property_failure(key, value);
        return err;
    }

    return 0;
}

int property_sync(const char *keys)
{
    #ifdef TARGET_MULTICORE
    if (keys == NULL) {
        LOG_D("property_sync input keys is nullptr");
        return -1;
    }

    char* key = (char *)(keys);
    char* cur_pos = key;
    char* end_pos = key + strlen(keys);

    while (cur_pos < end_pos) {
        cur_pos = strchr(key, ',');

        if (cur_pos == NULL) {
            cur_pos = end_pos;
        }

        int keylen = cur_pos - key;
        if (keylen <= 0 || keylen >= PROP_NAME_MAX) {
            ERROR("invalid key length %d, should 0 < keylen < %d: %s", keylen, PROP_NAME_MAX, key);
            return -1;
        }
        prop_msg_t msg;
        memset(&msg, 0, sizeof(msg));
        msg.cmd = PROP_MSG_SYNCPROP;
        strncpy(msg.name, key, keylen);
        LOG_D("property_sync `%s`", msg.name);
        int err = send_prop_msg(&msg, NULL, NULL);
        if (err < 0) {
            log_property_failure(key, NULL);
            return err;
        }
        key = cur_pos + 1;
    }
    #else
    LOG_W("property_sync is ignored in signle core platform `%s`", keys);
    #endif
    return 0;
}

int property_unsync(const char *keys)
{
    if (keys == NULL) {
        return -1;
    }

    #ifdef TARGET_MULTICORE
    char* key = (char *)(keys);
    char* cur_pos = key;
    char* end_pos = key + strlen(keys);

    while (cur_pos < end_pos) {
        cur_pos = strchr(key, ',');

        if (cur_pos == NULL) {
            cur_pos = end_pos;
        }

        int keylen = cur_pos - key;
        if (keylen <= 0 || keylen >= PROP_NAME_MAX) {
            ERROR("invalid key length %d, should 0 < keylen < %d: %s", keylen, PROP_NAME_MAX, key);
            return -1;
        }

        prop_msg_t msg;
        memset(&msg, 0, sizeof(msg));
        msg.cmd = PROP_MSG_UNSYNCPROP;
        strncpy(msg.name, key, keylen);
        LOG_D("property_unsync `%s`", msg.name);
        int err = send_prop_msg(&msg, NULL, NULL);
        if (err < 0) {
            log_property_failure(key, NULL);
            return err;
        }

        key = cur_pos + 1;
    }
    #else
    LOG_W("property_unsync is ignored in signle core platform `%s`", keys);
    #endif

    return 0;
}
